/* 
 * File: GLGame
 * Created: 14-JUN-2011
 * Author: Colin Gall-McDaid
 * 
 * Description: Android OpenGL ES implementation of the Game interface:
 * 
 *      - Perform window management
 *      - Use and manage WakeLock
 *      - Instantiate and hand out references to Graphics, Audio, FileIO and Input
 *      - Manage Screens and integrate them with the activity life cycle
 */
package com.gallsoft.games.framework.impl;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import com.gallsoft.games.bennyadv.Assets;
import com.gallsoft.games.bennyadv.Settings;
import com.gallsoft.games.framework.Audio;
import com.gallsoft.games.framework.FileIO;
import com.gallsoft.games.framework.Game;
import com.gallsoft.games.framework.Graphics;
import com.gallsoft.games.framework.Input;
import com.gallsoft.games.framework.Screen;

import android.app.Activity;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.view.Window;
import android.view.WindowManager;

public abstract class GLGame extends Activity implements Game, Renderer {

    enum GLGameState {
        Initialized, Running, Paused, Finished, Idle
    }

    private GLSurfaceView glView;
    private GLGraphics glGraphics;
    private Audio audio;
    private Input input;
    private FileIO fileIO;
    private Screen screen;
    private GLGameState state = GLGameState.Initialized;
    private Object stateChanged = new Object();
    private long startTime = System.nanoTime();
    private WakeLock wakeLock;
    boolean firstTimeCreate = true;

    /**
     * Instantiates all the required game objects and sets the content view to a
     * GL surface.
     */
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

        glView = new GLSurfaceView(this);
        glView.setRenderer(this);
        setContentView(glView);

        glGraphics = new GLGraphics(glView);
        fileIO = new AndroidFileIO(getAssets());
        audio = new AndroidAudio(this);
        input = new AndroidInput(this, glView, 1, 1);

        PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "GLGAME");
    }

    /**
     * Resume Android activity and notify game objects of resume event.
     */
    public void onResume() {
        super.onResume();

        glView.onResume();
        wakeLock.acquire();
    }

    /**
     * Pause Android activity. Set game state depending on whether we are really
     * pausing or actually finishing.
     */
    public void onPause() {
        synchronized (stateChanged) {
            if (isFinishing())
                state = GLGameState.Finished;
            else
                state = GLGameState.Paused;

            while (true) {
                try {
                    stateChanged.wait();
                    break;
                } catch (InterruptedException e) {
                }
            }
        }

        wakeLock.release();
        glView.onPause();
        super.onPause();
    }

    /**
     * Main screen update routine called as often as possible by the rendering
     * thread. Check the game state and react accordingly.
     * 
     * @param gl
     *            opengl context
     */
    public void onDrawFrame(GL10 gl) {
        GLGameState state = null;

        synchronized (stateChanged) {
            state = this.state;
        }

        switch (state) {
            case Running:
                float deltaTime = (System.nanoTime() - startTime) / 1000000000.0f;
                startTime = System.nanoTime();
                screen.update(deltaTime);
                screen.present(deltaTime);
                break;

            case Paused:
                screen.pause();
                synchronized (stateChanged) {
                    this.state = GLGameState.Idle;
                    stateChanged.notifyAll();
                }
                break;

            case Finished:
                screen.pause();
                screen.dispose();
                synchronized (stateChanged) {
                    this.state = GLGameState.Idle;
                    stateChanged.notifyAll();
                }
                break;
        }

        if (state == GLGameState.Running) {
            float deltaTime = (System.nanoTime() - startTime) / 1000000000.0f;
            startTime = System.nanoTime();

            screen.update(deltaTime);
            screen.present(deltaTime);
        }
    }

    public GLGraphics getGLGraphics() {
        return glGraphics;
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        // Do nothing
    }

    /**
     * Invoked from Renderer thread when game is started or restarted. Pass the
     * OpenGL context to the Android graphics module and set the game state and
     * screen.
     */
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        glGraphics.setGL(gl);

        if(firstTimeCreate) {
            Settings.load(getFileIO());
            Assets.load(this);
            firstTimeCreate = false;            
        } else {
            Assets.reload();
        }

        // Synchronize as values could be changed by pause method in UI thread
        synchronized (stateChanged) {
            if (state == GLGameState.Initialized)
                screen = getStartScreen();

            state = GLGameState.Running;
            screen.resume();
            startTime = System.nanoTime();
        }
    }

    @Override
    public Input getInput() {
        return input;
    }

    @Override
    public FileIO getFileIO() {
        return fileIO;
    }

    @Override
    public Graphics getGraphics() {
        throw new IllegalStateException("Not valid for OpenGL!");
    }

    @Override
    public Audio getAudio() {
        return audio;
    }

    /**
     * Set the screen, disposing of any existing one beforehand.
     * 
     * @param screen
     *            the screen to set
     */
    public void setScreen(Screen screen) {
        if (screen == null)
            throw new IllegalArgumentException("Screen must not be null");

        // Dispose of any existing screen
        this.screen.pause();
        this.screen.dispose();

        // Update this new screen
        screen.resume();
        screen.update(0);

        // Set the new screen object
        this.screen = screen;
    }

    @Override
    public Screen getCurrentScreen() {
        return screen;
    }

    @Override
    public Screen getStartScreen() {
        // TODO Auto-generated method stub
        return screen;
    }

}
